# 执行 SQL 并读取查询结果

使用 AI Studio SDK 在指定数据库中提交 SQL，并使用服务端返回的查询记录读取语句结果。需要保存 SQL 时，可创建工作簿和版本；工作簿与查询是相互独立的资源。

(sdk-ai-studio-sql-flow)=
## 任务流程

1. 选择目标数据库并准备 SQL 文本；必要时先读取数据库和表的元数据。
2. 提交 SQL 后，保存服务端返回的查询 ID。
3. 读取查询记录，从其中取得语句 ID。
4. 使用语句 ID 读取结果、详情或执行画像。

查询 ID 与语句 ID 不可互换。只有查询记录包含语句 ID 时，才能继续读取该语句的结果。

(sdk-ai-studio-sql-prepare)=
## 准备

| 需要的内容 | 在本页中的作用 |
| --- | --- |
| 已绑定目标工作区的客户端上下文 | 确定 SQL 操作所属的工作区。 |
| 目标数据库名称 | 确定 SQL 的执行目标。 |
| SQL 文本 | 提交查询或保存工作簿版本。 |
| 可选的工作簿名称 | 保存 SQL 时创建工作簿。 |

写入、删除和数据定义语句会影响数据。运行前确认 SQL 文本、目标工作区、数据库和权限。

(sdk-ai-studio-sql-run)=
## 提交 SQL 并读取结果

示例先创建一个工作簿版本，再提交 SQL。提交返回查询 ID，不返回结果行；查询记录提供语句 ID，结果读取使用该语句 ID。

:::::{tab-set}
:sync-group: sdk-language

::::{tab-item} Go
:sync: go

```go
import (
	"context"
	"fmt"

	sdk "github.com/matrixorigin/matrixflow/sdk/go-sdk"
)

func runSQL(ctx context.Context, workspace *sdk.WorkspaceHandle, databaseName, workbookName, sqlText string) error {
	metadata := workspace.SQLMetadata()
	database, err := metadata.Database(databaseName)
	if err != nil {
		return err
	}
	if _, err := database.Tables(ctx); err != nil {
		return err
	}

	workbook, createdWorkbook, err := workspace.CreateSQLWorkbook(ctx, workbookName)
	if err != nil {
		return err
	}
	if workbook.ID() != createdWorkbook.GetWorkbookId() {
		return fmt.Errorf("workbook handle and result do not match")
	}
	version, createdVersion, err := workbook.CreateVersion(ctx, sqlText)
	if err != nil {
		return err
	}
	if version.ID() != createdVersion.GetId() {
		return fmt.Errorf("workbook version handle and result do not match")
	}
	if err := version.Save(ctx); err != nil {
		return err
	}

	query, started, err := workspace.ExecuteSQL(
		ctx, sqlText, sdk.WithSQLQueryDBName(databaseName), sdk.WithSQLQueryLimit(100),
	)
	if err != nil {
		return err
	}
	if query.ID() != started.GetQueryId() {
		return fmt.Errorf("query handle and result do not match")
	}
	described, err := query.Describe(ctx)
	if err != nil {
		return err
	}
	if described.GetStatementId() == "" {
		return fmt.Errorf("query %q has no statement id", query.ID())
	}
	statement, err := workspace.SQLStatement(described.GetStatementId())
	if err != nil {
		return err
	}
	result, err := statement.Result(ctx, sdk.WithSQLResultLimit(100))
	if err != nil {
		return err
	}
	_ = result
	return nil
}
```

::::

::::{tab-item} Python
:sync: python

```python
import moi_product_sdk as sdk


def run_sql(workspace, database_name, workbook_name, sql_text):
    metadata = workspace.sql_metadata()
    database = metadata.database(database_name)
    database.tables()

    workbook, created_workbook = workspace.create_sql_workbook(workbook_name)
    if workbook.id != created_workbook.workbook_id:
        raise RuntimeError("workbook handle and result do not match")
    version, created_version = workbook.create_version(sql_text)
    if version.id != created_version.id:
        raise RuntimeError("workbook version handle and result do not match")
    version.save()

    query, started = workspace.execute_sql(
        sql_text,
        sdk.with_sql_query_db_name(database_name),
        sdk.with_sql_query_limit(100),
    )
    if query.id != started.query_id:
        raise RuntimeError("query handle and result do not match")
    described = query.describe()
    if not described.statement_id:
        raise RuntimeError(f"query {query.id} has no statement id")
    statement = workspace.sql_statement(described.statement_id)
    return statement.result(sdk.with_sql_result_limit(100))
```

::::

:::::

结果对象包含当前结果读取的分页信息和结果数据。需要检查执行记录或执行画像时，继续使用同一语句 ID 读取对应信息。

(sdk-ai-studio-sql-limitations)=
## 限制

- 工作簿、查询和语句都需要已有 ID；空 ID 会在客户端被拒绝。
- 取消查询是独立操作。已结束查询的取消可能被拒绝，因此先读取查询记录确认状态。
- 删除数据库、表、视图或工作簿会影响对应对象；删除工作簿后不要继续使用原对象。

(sdk-ai-studio-sql-next)=
## 下一步

- 管理 SQL、工作簿和 SQL 历史的 HTTP 接口以 API Reference 中的数据处理接口为准。
